namespace Angelus.Oracle.Entity.OracleClient.OracleGen
{
    using System;
    using System.Data.Common.CommandTrees;
    using System.Data.Metadata.Edm;
    using System.Diagnostics;
    using System.Globalization;
    using System.Text;
    using Common;
    using Common.Entity;

    /// <summary>
    /// Description of OracleQueryGenerator.
    /// </summary>
    internal class OracleQueryGenerator : SqlGenerator
    {
        #region Constants

        private static readonly char[] HexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D' , 'E', 'F' };

        #endregion

        #region Constructors

        /// <summary>
        /// 
        /// </summary>
        public OracleQueryGenerator()
        {
        }

        #endregion

        #region QueryGenerator Overrides

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public override ISqlFragment Visit(DbConstantExpression expression)
        {
            PrimitiveTypeKind typeKind;
            if (!MetadataHelpers.TryGetPrimitiveTypeKind(expression.ResultType, out typeKind))
            {
                throw new NotSupportedException();
            }

            var builder = new SqlBuilder();
            if (PrimitiveTypeKind.DateTime != typeKind)
            {
                if (PrimitiveTypeKind.Time == typeKind)
                {
                    builder.Append("CAST(");
                    var span = (TimeSpan) expression.Value;
                    builder.Append(
                        this.QuoteIdentifier(string.Format("{0} {1}:{2}:{3}.{4}",
                                                           new object[]
                                                               {
                                                                   span.Days, span.Hours, span.Minutes, span.Seconds,
                                                                   span.Milliseconds
                                                               })));
                    builder.Append(" AS INTERVAL DAY TO SECOND)");
                    return builder;
                }

                return base.Visit(expression);
            }

            builder.Append("TO_DATE(");
            var time = (DateTime) expression.Value;
            builder.Append(this.QuoteIdentifier(time.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)));
            builder.Append(", 'yyyy-mm-dd hh24:mi:ss')");
            return builder;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public override ISqlFragment Visit(DbLimitExpression expression)
        {
            SqlSelectStatement statement = VisitExpressionEnsureSqlStatement(expression.Argument, false);
            if (!IsCompatible(statement, expression.ExpressionKind))
            {
                Symbol symbol;
                TypeUsage usage = MetadataHelpers.GetElementTypeUsage(expression.Argument.ResultType);
                statement = CreateNewSelectStatement(statement, "top", usage, out symbol);
                AddFromSymbol(statement, "top", symbol, false);
            }

            if (!statement.Where.IsEmpty)
            {
                statement.Where.Append(" AND ");
            }

            statement.Where.Append(" ROWNUM <= (");
            statement.Where.Append(((DbConstantExpression) expression.Limit).Value.ToString());
            statement.Where.Append(")");
            return statement;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public override ISqlFragment Visit(DbNewInstanceExpression expression)
        {
            if ((1 == expression.Arguments.Count) &&
                (DbExpressionKind.Element == expression.Arguments[0].ExpressionKind))
            {
                var elementExpression = expression.Arguments[0] as DbElementExpression;
                SqlSelectStatement statement = VisitExpressionEnsureSqlStatement(elementExpression.Argument);
                if (!IsCompatible(statement, DbExpressionKind.Element))
                {
                    Symbol symbol;
                    TypeUsage usage = MetadataHelpers.GetElementTypeUsage(elementExpression.Argument.ResultType);
                    statement = CreateNewSelectStatement(statement, "element", usage, out symbol);
                    AddFromSymbol(statement, "element", symbol, false);
                }

                return statement;
            }

            var edmType = (CollectionType) expression.ResultType.EdmType;
            var builder = new SqlBuilder();
            if (0 == expression.Arguments.Count)
            {
                builder.Append(" SELECT ");
                this.VisitCastExpression(builder, null, edmType.TypeUsage);
                builder.Append(") AS X FROM (SELECT 1 FROM DUAL) AS Y WHERE 1 = 0");
            }

            string joinType = string.Empty;
            foreach (DbExpression item in expression.Arguments)
            {
                builder.Append(joinType);
                builder.Append(" SELECT ");
                builder.Append(item.Accept(this));
                if (BuiltInTypeKind.PrimitiveTypeKind == edmType.TypeUsage.EdmType.BuiltInTypeKind)
                {
                    builder.Append(" AS X ");
                }

                joinType = " UNION ALL ";
            }

            builder.Append(" FROM DUAL");
            return builder;
        }

        /// <summary>
        /// 
        /// </summary>
        private static void InitializeCanonicalFunctors()
        {
            CanonicalFunctors.Add("substring", "SUBSTR");
            CanonicalFunctors.Add("tolower", "LOWER");
            CanonicalFunctors.Add("toupper", "UPPER");
//            CanonicalFunctors.Add("concat", "CONCAT");
            CanonicalFunctors.Add("padleft", "LPAD");
            CanonicalFunctors.Add("padright", "RPAD");
//            CanonicalFunctors.Add("replace", "REPLACE");
            CanonicalFunctors.Add("trimend", "RTRIM");
            CanonicalFunctors.Add("trimstart", "LTRIM");

            CanonicalFunctors.Add("ceiling", "CEIL");

            CanonicalFunctors.Add("newguid", "SYS_GUID");
        }

        #endregion

        #region Private Implementation

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        internal override string QuoteIdentifier(string name)
        {
            Debug.Assert(!string.IsNullOrEmpty(name));
            // We assume that the names are not quoted to begin with.
            return "\"" + name.Replace("\"", "\"\"") + "\"";
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <param name="isUnicode"></param>
        /// <returns></returns>
        protected override string EscapeSingleQuote(string value, bool isUnicode)
        {
            return (isUnicode ? "N'" : "'") + value.Replace("'", "''") + "'";
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="fragment"></param>
        /// <param name="type"></param>
        protected override void VisitCastExpression(SqlBuilder builder, ISqlFragment fragment, TypeUsage type)
        {
            var edmType = (PrimitiveType) type.EdmType;

            string castType = string.Empty;

            switch (edmType.PrimitiveTypeKind)
            {
                case PrimitiveTypeKind.Binary:
                    builder.Append("TO_BLOB(");
                    builder.Append(fragment);
                    builder.Append(")");
                    return;

                case PrimitiveTypeKind.Byte:
                    castType = " AS NUMBER)";
                    break;

                case PrimitiveTypeKind.DateTime:
                    castType = " AS DATE)";
                    break;

                case PrimitiveTypeKind.Decimal:
                    castType = " AS NUMBER)";
                    break;

                case PrimitiveTypeKind.Double:
                    castType = " AS NUMBER)";
                    break;

                case PrimitiveTypeKind.Guid:
                    throw new NotSupportedException("Casting to 'System.Guid' type is not supported.");

                case PrimitiveTypeKind.Single:
                    castType = " AS BINARY_FLOAT)";
                    break;

                case PrimitiveTypeKind.SByte:
                case PrimitiveTypeKind.Int16:
                case PrimitiveTypeKind.Int32:
                case PrimitiveTypeKind.Int64:
                    builder.Append("FLOOR(CAST(");
                    builder.Append(fragment);
                    builder.Append(" AS NUMBER))");
                    return;

                case PrimitiveTypeKind.String:
                    builder.Append("TO_CHAR(");
                    builder.Append(fragment);
                    builder.Append(")");
                    return;

                case PrimitiveTypeKind.Time:
                    castType = " AS INTERVAL DAY TO SECOND)";
                    break;
            }

            builder.Append("CAST(");
            builder.Append(fragment);
            builder.Append(castType);
        }

        #endregion

        #region Function Handling Helpers

        /// <summary>
        /// 
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        private static string ByteArrayToBinaryString(Byte[] bytes)
        {
            var builder = new StringBuilder(bytes.Length*2);
            for (int index = 0, max = bytes.Length; index < max; index++)
            {
                builder.Append(HexDigits[(bytes[index] & 0xF0) >> 4]).Append(HexDigits[bytes[index] & 0xF0]);
            }

            return builder.ToString();
        }

        /// <summary>
        /// Function rename Substring to Substr
        /// </summary>
        /// <param name="generator"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        private static ISqlFragment HandleCanonicalFunctionSubstring(SqlGenerator generator,
                                                                     DbFunctionExpression expression)
        {
            // TODO  : return generator.HandleDefaultFunction(expression, "SUBSTR");
            return null;
        }

        /// <summary>
        /// Function NewGuid to SYS_GUID
        /// </summary>
        /// <param name="generator"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        private static ISqlFragment HandleCanonicalFunctionNewGuid(SqlGenerator generator,
                                                                   DbFunctionExpression expression)
        {
            // TODO : return generator.HandleDefaultFunction(expression, "SYS_GUID");
            return null;
        }

        /// <summary>
        /// Function LEFT
        /// </summary>
        /// <param name="generator"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        private static ISqlFragment HandleCanonicalFunctionLeft(DbExpressionVisitor<ISqlFragment> generator,
                                                                DbFunctionExpression expression)
        {
            var builder = new SqlBuilder();

            builder.Append("SUBSTR( ");
            builder.Append(expression.Arguments[0].Accept(generator));
            builder.Append(", 1, ");
            builder.Append(expression.Arguments[1].Accept(generator));
            builder.Append(")");

            return builder;
        }

        /// <summary>
        /// Function RIGHT
        /// </summary>
        /// <param name="generator"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        private static ISqlFragment HandleCanonicalFunctionRight(DbExpressionVisitor<ISqlFragment> generator,
                                                                 DbFunctionExpression expression)
        {
            var builder = new SqlBuilder();

            builder.Append("(CASE WHEN LENGTH(");
            builder.Append(expression.Arguments[0].Accept(generator));
            builder.Append(") >= (");
            builder.Append(expression.Arguments[1].Accept(generator));
            builder.Append(" THEN ");
            builder.Append(" SUBSTR(");
            builder.Append(expression.Arguments[0].Accept(generator));
            builder.Append(", -(");
            builder.Append(expression.Arguments[1].Accept(generator));
            builder.Append("),");
            builder.Append(expression.Arguments[1].Accept(generator));
            builder.Append(")");
            builder.Append(" ELSE ");
            builder.Append(expression.Arguments[0].Accept(generator));
            builder.Append(" END)");

            return builder;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="generator"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        private static ISqlFragment HandleCanonicalFunctionBitwise(DbExpressionVisitor<ISqlFragment> generator,
                                                                   DbFunctionExpression expression)
        {
            var builder = new SqlBuilder();
            switch (expression.Function.Name.ToUpperInvariant())
            {
                case "BITWISEAND":
                    //builder.Append("TO_NUMBER(SYS.UTL_RAW.BIT_AND(");
                    //builder.Append(FragmentToBinaryString(expression.Arguments[0].Accept(generator)));
                    //builder.Append(",");
                    //builder.Append(FragmentToBinaryString(expression.Arguments[1].Accept(generator)));
                    //builder.Append("))");
                    //generator.HandleDefaultFunction(expression, "SYS.UTL_RAW.BIT_AND");
                    break;

                case "BITWISEOR":
                    //builder.Append("TO_NUMBER(SYS.UTL_RAW.BIT_OR(");
                    //builder.Append(FragmentToBinaryString(expression.Arguments[0].Accept(generator)));
                    //builder.Append(",");
                    //builder.Append(FragmentToBinaryString(expression.Arguments[1].Accept(generator)));
                    //builder.Append("))");
                    //generator.HandleDefaultFunction(expression, "SYS.UTL_RAW.BIT_OR");
                    break;

                case "BITWISEXOR":
                    //builder.Append("TO_NUMBER(SYS.UTL_RAW.BIT_XOR(");
                    //builder.Append(FragmentToBinaryString(expression.Arguments[0].Accept(generator)));
                    //builder.Append(",");
                    //builder.Append(FragmentToBinaryString(expression.Arguments[1].Accept(generator)));
                    //builder.Append("))");
                    //generator.HandleDefaultFunction(expression, "SYS.UTL_RAW.BIT_XOR");
                    break;

                    case "BITWISENOT":
                    //    builder.Append("TO_NUMBER(SYS.UTL_RAW.BIT_COMPLEMENT(");
                    //    builder.Append(FragmentToBinaryString(expression.Arguments[0].Accept(generator)));
                    //    builder.Append("))");
                    //generator.HandleDefaultFunction(expression, "SYS.UTL_RAW.BIT_COMPLEMENT");
                    break;
            }

            return builder;
        }

        #endregion
    }
}